﻿using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;
using Celeriq.Common;
using Celeriq.Utilities;

namespace Celeriq.Server.Core
{
    internal static class StatLogger
    {
        private const int LOG_TIMER_INTERVAL = 30 * 1000;
        private static bool _ready = false;
        private static DateTime _pivot = new DateTime(2000, 1, 1);
        private static Dictionary<Guid, List<RepositorySummmaryStats>> _statCache = new Dictionary<Guid, List<RepositorySummmaryStats>>();
        private static System.Timers.Timer _timer = null;

        private static string ConnectionString
        {
            get
            {
                var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
                var filename = Path.Combine(fi.DirectoryName, "stats.db3");
                return @"Data Source=" + filename;
            }
        }

        #region Init

        public static void Initialize()
        {
            try
            {
                #region Server table
                {
                    var sql = @"CREATE TABLE IF NOT EXISTS [Stats] (
                          [Key] TEXT(32) NOT NULL PRIMARY KEY,
                          [Timestamp] INTEGER(4) NOT NULL,
                          [MemoryUsageTotal] INTEGER(8) NOT NULL,
                          [MemoryUsageAvailable] INTEGER(8) NOT NULL,
                          [RepositoryInMem] INTEGER(2) NOT NULL,
                          [RepositoryLoadDelta] INTEGER(2) NOT NULL,
                          [RepositoryUnloadDelta] INTEGER(2) NOT NULL,
                          [RepositoryTotal] INTEGER(2) NOT NULL,
                          [RepositoryCreateDelta] INTEGER(2) NOT NULL,
                          [RepositoryDeleteDelta] INTEGER(2) NOT NULL,
                          [ProcessorUsage] INTEGER(1) NOT NULL
                          )";
                    RunScript(sql);

                    sql = "CREATE INDEX IF NOT EXISTS IDX_STATS_TIMESTAMP ON [Stats] (Timestamp)";
                    RunScript(sql);
                }
                #endregion

                #region Repository table
                {
                    var sql = @"CREATE TABLE IF NOT EXISTS [RepositoryStats] (
                          [Key] INTEGER NOT NULL PRIMARY KEY,
                          [RepositoryId] TEXT(32) NOT NULL,
                          [Timestamp] INTEGER(4) NOT NULL,
                          [ActionType] INTEGER NOT NULL,
                          [Elapsed] INTEGER NOT NULL,
                          [Count] INTEGER NOT NULL
                          )";
                    RunScript(sql);

                    sql = "CREATE INDEX IF NOT EXISTS IDX_REPOSITORYSTATS_TIMESTAMP ON [Stats] (Timestamp)";
                    RunScript(sql);
                }
                #endregion

                //Log repository stats every N seconds
                _timer = new System.Timers.Timer(LOG_TIMER_INTERVAL);
                _timer.AutoReset = false;
                _timer.Elapsed += TimerTick;
                _timer.Start();

                _ready = true;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static void Shutdown()
        {
            try
            {
                _timer.Stop();
                TimerTick(null, null);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        private static void RunScript(string sql)
        {
            try
            {
                using (var connection = new System.Data.SQLite.SQLiteConnection(ConnectionString))
                {
                    using (var command = new System.Data.SQLite.SQLiteCommand(connection))
                    {
                        connection.Open();
                        command.CommandText = sql;
                        command.ExecuteNonQuery();
                    }
                }
            }
            catch (Exception ec)
            {
                throw;
            }
        }

        #endregion

        #region Server Stats

        public static void Log(RealtimeStats item)
        {
            if (!_ready) return;

            try
            {
                var timestamp = (int) DateTime.Now.Subtract(_pivot).TotalSeconds;
                var sql = "INSERT INTO [Stats] ([Key],[Timestamp],[MemoryUsageTotal],[MemoryUsageAvailable],[RepositoryInMem],[RepositoryLoadDelta],[RepositoryUnloadDelta],[RepositoryTotal],[RepositoryCreateDelta],[RepositoryDeleteDelta],[ProcessorUsage]) Values (" +
                          "'" + Guid.NewGuid() + "'," +
                          timestamp + "," +
                          item.MemoryUsageTotal + "," +
                          item.MemoryUsageAvailable + "," +
                          item.RepositoryInMem + "," +
                          item.RepositoryLoadDelta + "," +
                          item.RepositoryUnloadDelta + "," +
                          item.RepositoryTotal + "," +
                          item.RepositoryCreateDelta + "," +
                          item.RepositoryDeleteDelta + "," +
                          item.ProcessorUsage +
                          ")";
                RunScript(sql);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static List<RealtimeStats> QueryServerStats(DateTime start, DateTime end)
        {
            if (!_ready) return null;

            try
            {
                var retval = new List<RealtimeStats>();
                using (var connection = new System.Data.SQLite.SQLiteConnection(ConnectionString))
                {
                    using (var command = new System.Data.SQLite.SQLiteCommand(connection))
                    {
                        var sv = (int)start.Subtract(_pivot).TotalSeconds;
                        var ev = (int)end.Subtract(_pivot).TotalSeconds;

                        connection.Open();
                        command.CommandText = "SELECT * FROM [Stats] WHERE '" + sv + "' <= [Timestamp] AND [Timestamp] <= '" + ev + "' ORDER BY [Timestamp]";
                        using (var reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                retval.Add(new RealtimeStats
                                           {
                                               Timestamp = _pivot.AddSeconds((long)reader["Timestamp"]),
                                               MemoryUsageAvailable = (long)reader["MemoryUsageAvailable"],
                                               MemoryUsageTotal = (long)reader["MemoryUsageTotal"],
                                               ProcessorUsage = (int)(long)reader["ProcessorUsage"],
                                               RepositoryCreateDelta = (int)(long)reader["RepositoryCreateDelta"],
                                               RepositoryDeleteDelta = (int)(long)reader["RepositoryDeleteDelta"],
                                               RepositoryInMem = (int)(long)reader["RepositoryInMem"],
                                               RepositoryLoadDelta = (int)(long)reader["RepositoryLoadDelta"],
                                               RepositoryTotal = (int)(long)reader["RepositoryTotal"],
                                               RepositoryUnloadDelta = (int)(long)reader["RepositoryUnloadDelta"],
                                           });
                            }
                        }

                    }
                }
                return retval;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                return null;
            }
        }

        #endregion

        #region Repository Stats

        private static void TimerTick(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!_ready) return;

            _timer.Stop();
            try
            {
                //Lock the stats list and build queries
                Dictionary<Guid, List<RepositorySummmaryStats>> copyCache = null;
                lock (_statCache)
                {
                    copyCache = _statCache.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
                    _statCache.Clear();
                }

                using (var connection = new System.Data.SQLite.SQLiteConnection(ConnectionString))
                {
                    connection.Open();
                    using (var mytransaction = connection.BeginTransaction())
                    {
                        using (var mycommand = new SQLiteCommand(connection))
                        {
                            mycommand.CommandText = "INSERT INTO [RepositoryStats] ([Timestamp],[ActionType],[Elapsed],[RepositoryId],[Count]) Values (?,?,?,?,?)";
                            mycommand.Parameters.Add(new SQLiteParameter { DbType = System.Data.DbType.Int64, ParameterName = "Timestamp" });
                            mycommand.Parameters.Add(new SQLiteParameter { DbType = System.Data.DbType.Int64, ParameterName = "ActionType" });
                            mycommand.Parameters.Add(new SQLiteParameter { DbType = System.Data.DbType.Int64, ParameterName = "Elapsed" });
                            mycommand.Parameters.Add(new SQLiteParameter { DbType = System.Data.DbType.String, ParameterName = "RepositoryId"});
                            mycommand.Parameters.Add(new SQLiteParameter { DbType = System.Data.DbType.Int64, ParameterName = "Count" });

                            var sqlList = new List<string>();
                            var typeValues = Enum.GetValues(typeof(RepositoryActionConstants)).Cast<RepositoryActionConstants>().ToList();
                            var timestamp = (int)DateTime.Now.Subtract(_pivot).TotalSeconds;
                            foreach (var t in typeValues)
                            {
                                foreach (var key in copyCache.Keys)
                                {
                                    var queryList = copyCache[key].Where(x => x.ActionType == t).ToList();
                                    var elapsed = queryList.Sum(x => x.Elapsed); //Total elapsed time
                                    var count = queryList.Count; //Number of queries

                                    mycommand.Parameters[0].Value = timestamp;
                                    mycommand.Parameters[1].Value = int.Parse(t.ToString("d"));
                                    mycommand.Parameters[2].Value = elapsed;
                                    mycommand.Parameters[3].Value = key;
                                    mycommand.Parameters[4].Value = count;
                                    mycommand.ExecuteNonQuery();
                                }
                            }

                        }
                        mytransaction.Commit();
                    }
                }

            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
            finally
            {
                _timer.Start();
            }
        }

        public static void Log(RepositorySummmaryStats item)
        {
            try
            {
                lock (_statCache)
                {
                    if (!_statCache.ContainsKey(item.RepositoryId))
                        _statCache.Add(item.RepositoryId, new List<RepositorySummmaryStats>());
                    _statCache[item.RepositoryId].Add(item);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static RepositorySummmaryStats QueryRepositoryStats(Guid repositoryId, DateTime start, DateTime end)
        {
            if (!_ready) return null;

            try
            {
                var retval = new RepositorySummmaryStats() { ActionType = RepositoryActionConstants.Query };
                using (var connection = new System.Data.SQLite.SQLiteConnection(ConnectionString))
                {
                    using (var command = new System.Data.SQLite.SQLiteCommand(connection))
                    {

                        var sv = (int) start.Subtract(_pivot).TotalSeconds;
                        var ev = (int) end.Subtract(_pivot).TotalSeconds;

                        connection.Open();
                        if (repositoryId == Guid.Empty)
                            command.CommandText = "SELECT * FROM [RepositoryStats] WHERE '" + sv + "' <= [Timestamp] AND [Timestamp] <= '" + ev + "' AND ActionType = " + (int) RepositoryActionConstants.Query + " ORDER BY [Timestamp]";
                        else
                            command.CommandText = "SELECT * FROM [RepositoryStats] WHERE  [RepositoryId] = '" + repositoryId.ToString() + "' AND '" + sv + "' <= [Timestamp] AND [Timestamp] <= '" + ev + "' AND ActionType = " + (int) RepositoryActionConstants.Query + " ORDER BY [Timestamp]";

                        long count = 0;
                        long totalElapsed = 0;
                        using (var reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                count += (long) reader["Count"];
                                totalElapsed += (long) reader["Elapsed"];
                            }
                        }

                        var elapsedPer = 0.0;
                        if (count > 0) elapsedPer = ((totalElapsed * 1.0) / count);

                        //_pivot.AddSeconds((long)reader["Timestamp"]),
                        //(RepositoryActionConstants)(long)reader["ActionType"]
                        retval.QueryCount = (int) count;
                        retval.Elapsed = (decimal)elapsedPer;
                        retval.RepositoryId = repositoryId;
                    }
                }
                return retval;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                return null;
            }
        }

        #endregion
    }
}
